{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import pydeck as pdk\n",
    "pn.extension('deckgl')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Deck.gl](https://deck.gl/#/) is a very powerful WebGL-powered framework for visual exploratory data analysis of large datasets. The `DeckGL` *pane* renders JSON Deck.gl JSON specification as well as `PyDeck` plots inside a panel. If data is encoded in the deck.gl layers the pane will extract the data and send it across the websocket in a binary format speeding up rendering.\n",
    "\n",
    "The [`PyDeck`](https://deckgl.readthedocs.io/en/latest/) *package* provides Python bindings. Please follow the [installation instructions](https://github.com/uber/deck.gl/blob/master/bindings/pydeck/README.md) closely to get it working in this Jupyter Notebook.\n",
    "\n",
    "#### Parameters:\n",
    "\n",
    "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
    "\n",
    "* **``mapbox_api_key``** (string): The MapBox API key if not supplied by a PyDeck object.\n",
    "* **``object``** (object, dict or string): The deck.GL JSON or PyDeck object being displayed\n",
    "* **``tooltips``** (bool or dict, default=True): Whether to enable tooltips or custom tooltip formatters\n",
    "* **``throttle``** (dict, default={'view': 200, 'hover': 200}): Throttling timeouts (in milliseconds) for view state and hover events. \n",
    "\n",
    "In addition to parameters which control how the object is displayed the DeckGL pane also exposes a number of parameters which receive updates from the plot:\n",
    "\n",
    "* **``click_state``** (dict): Contains the last click event on the DeckGL plot.\n",
    "* **``hover_state``** (dict): Contains information about the current hover location on the DeckGL plot.\n",
    "* **``view_state``** (dict): Contains information about the current view port of the DeckGL plot.\n",
    "\n",
    "____"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to use Deck.gl you need a MAP BOX Key which you can acquire for free for limited use at [mapbox.com](https://account.mapbox.com/access-tokens/).\n",
    "\n",
    "Now we can define a JSON spec and pass it to the DeckGL pane along with the Mapbox key (if you have one):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAPBOX_KEY = \"\"\n",
    "\n",
    "if MAPBOX_KEY:\n",
    "    map_style = \"mapbox://styles/mapbox/dark-v9\"\n",
    "else:\n",
    "    map_style = \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\"\n",
    "\n",
    "json_spec = {\n",
    "    \"initialViewState\": {\n",
    "        \"bearing\": -27.36,\n",
    "        \"latitude\": 52.2323,\n",
    "        \"longitude\": -1.415,\n",
    "        \"maxZoom\": 15,\n",
    "        \"minZoom\": 5,\n",
    "        \"pitch\": 40.5,\n",
    "        \"zoom\": 6\n",
    "    },\n",
    "    \"layers\": [{\n",
    "        \"@@type\": \"HexagonLayer\",\n",
    "        \"autoHighlight\": True,\n",
    "        \"coverage\": 1,\n",
    "        \"data\": \"https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv\",\n",
    "        \"elevationRange\": [0, 3000],\n",
    "        \"elevationScale\": 50,\n",
    "        \"extruded\": True,\n",
    "        \"getPosition\": \"@@=[lng, lat]\",\n",
    "        \"id\": \"8a553b25-ef3a-489c-bbe2-e102d18a3211\",\n",
    "        \"pickable\": True\n",
    "    }],\n",
    "    \"mapStyle\": map_style,\n",
    "    \"views\": [\n",
    "        {\"@@type\": \"MapView\", \"controller\": True}\n",
    "    ]\n",
    "}\n",
    "\n",
    "deck_gl = pn.pane.DeckGL(json_spec, mapbox_api_key=MAPBOX_KEY, sizing_mode='stretch_width', height=600)\n",
    "\n",
    "deck_gl"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you do not have a Mapbox API key you can use one of the [Carto basemaps](https://deck.gl/docs/api-reference/carto/basemap#supported-basemaps).\n",
    "\n",
    "Like other panes the DeckGL object can be replaced or updated. In this example we will change the `colorRange` of the HexagonLayer and then trigger an update:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "COLOR_RANGE = [\n",
    "      [1, 152, 189],\n",
    "      [73, 227, 206],\n",
    "      [216, 254, 181],\n",
    "      [254, 237, 177],\n",
    "      [254, 173, 84],\n",
    "      [209, 55, 78]\n",
    "]\n",
    "\n",
    "json_spec['layers'][0]['colorRange'] = COLOR_RANGE\n",
    "\n",
    "deck_gl.param.trigger('object')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tooltips\n",
    "\n",
    "By default tooltips can be disabled and enabled by setting `tooltips=True/False`. For more customization it is possible to pass in a dictionary defining the formatting. Let us start by declaring a plot with two layers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "DATA_URL = 'https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/geojson/vancouver-blocks.json'\n",
    "\n",
    "LAND_COVER = [[[-123.0, 49.196], [-123.0, 49.324], [-123.306, 49.324], [-123.306, 49.196]]]\n",
    "\n",
    "json_spec = {\n",
    "    \"initialViewState\": {\n",
    "      'latitude': 49.254,\n",
    "      'longitude': -123.13,\n",
    "      'zoom': 11,\n",
    "      'maxZoom': 16,\n",
    "      'pitch': 45,\n",
    "      'bearing': 0\n",
    "    },\n",
    "    \"layers\": [{\n",
    "        '@@type': 'GeoJsonLayer',\n",
    "        'id': 'geojson',\n",
    "        'data': DATA_URL,\n",
    "        'opacity': 0.8,\n",
    "        'stroked': True,\n",
    "        'filled': True,\n",
    "        'extruded': True,\n",
    "        'wireframe': True,\n",
    "        'fp64': True,\n",
    "        'getLineColor': [255, 255, 255],\n",
    "        'getElevation': \"@@=properties.valuePerSqm / 20\",\n",
    "        'getFillColor': \"@@=[255, 255, properties.growth * 255]\",\n",
    "        'pickable': True,\n",
    "    }, {\n",
    "        '@@type': 'PolygonLayer',\n",
    "        'id': 'landcover',\n",
    "        'data': LAND_COVER,\n",
    "        'stroked': True,\n",
    "        'pickable': True,\n",
    "        # processes the data as a flat longitude-latitude pair\n",
    "        'getPolygon': '@@=-',\n",
    "        'getFillColor': [0, 0, 0, 20]\n",
    "    }],\n",
    "    \"mapStyle\": map_style,\n",
    "    \"views\": [\n",
    "        {\"@@type\": \"MapView\", \"controller\": True}\n",
    "    ]\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have explicitly given these layers the `id` `'landcover'` and `'geojson'`. Ordinarily we wouldn't enable `pickable` property on the 'landcover' polygon and if we only have a single `pickable` layer it is sufficient to declare a tooltip like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "geojson_tooltip = {\n",
    "    \"html\": \"\"\"\n",
    "      <b>Value per Square meter:</b> {properties.valuePerSqm}<br>\n",
    "      <b>Growth:</b> {properties.growth}\n",
    "    \"\"\",\n",
    "    \"style\": {\n",
    "        \"backgroundColor\": \"steelblue\",\n",
    "        \"color\": \"white\"\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we created an HTML template which is populated by the `properties` in the GeoJSON and then has the `style` applied. In general the dictionary may contain:\n",
    "\n",
    "- `html` - Set the innerHTML of the tooltip.\n",
    "\n",
    "- `text` - Set the innerText of the tooltip.\n",
    "\n",
    "- `style` - A dictionary of CSS styles that will modify the default style of the tooltip.\n",
    "\n",
    "If we have multiple pickable layers we can declare distinct tooltips by nesting the tooltips dictionary, indexed by the layer `id` or the index of the layer in the list of layers (note that the dictionary must be either integer indexed or string indexed not both)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tooltip = {\n",
    "    \"geojson\": geojson_tooltip,\n",
    "    \"landcover\": {\n",
    "        \"html\": \"The background\",\n",
    "        \"style\": {\n",
    "            \"backgroundColor\": \"red\",\n",
    "            \"color\": \"white\"\n",
    "       }\n",
    "    }\n",
    "}\n",
    "\n",
    "pn.pane.DeckGL(json_spec, tooltips=tooltip, mapbox_api_key=MAPBOX_KEY, sizing_mode='stretch_width', height=600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When hovering on the area around Vancouver you should now see a tooltip saying `'The background'` colored red, while the hover tooltip should show information about each property when hovering over one of the property polygons."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PyDeck\n",
    "\n",
    "Instead of writing out raw JSON-like dictionaries the `DeckGL` pane may also be given a PyDeck object to render:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pydeck\n",
    "\n",
    "DATA_URL = \"https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/geojson/vancouver-blocks.json\"\n",
    "\n",
    "LAND_COVER = [[[-123.0, 49.196], [-123.0, 49.324], [-123.306, 49.324], [-123.306, 49.196]]]\n",
    "\n",
    "INITIAL_VIEW_STATE = pydeck.ViewState(\n",
    "  latitude=49.254,\n",
    "  longitude=-123.13,\n",
    "  zoom=11,\n",
    "  max_zoom=16,\n",
    "  pitch=45,\n",
    "  bearing=0\n",
    ")\n",
    "\n",
    "polygon = pydeck.Layer(\n",
    "    'PolygonLayer',\n",
    "    LAND_COVER,\n",
    "    stroked=False,\n",
    "    # processes the data as a flat longitude-latitude pair\n",
    "    get_polygon='-',\n",
    "    get_fill_color=[0, 0, 0, 20]\n",
    ")\n",
    "\n",
    "geojson = pydeck.Layer(\n",
    "    'GeoJsonLayer',\n",
    "    DATA_URL,\n",
    "    opacity=0.8,\n",
    "    stroked=False,\n",
    "    filled=True,\n",
    "    extruded=True,\n",
    "    wireframe=True,\n",
    "    get_elevation='properties.valuePerSqm / 20',\n",
    "    get_fill_color='[255, 255, properties.growth * 255]',\n",
    "    get_line_color=[255, 255, 255],\n",
    "    pickable=True\n",
    ")\n",
    "\n",
    "r = pydeck.Deck(\n",
    "    api_keys={'mapbox': MAPBOX_KEY},\n",
    "    layers=[polygon, geojson],\n",
    "    map_style=map_style,\n",
    "    initial_view_state=INITIAL_VIEW_STATE\n",
    ")\n",
    "\n",
    "# Tooltip (you can get the id directly from the layer object)\n",
    "tooltips = {geojson.id: geojson_tooltip}\n",
    "\n",
    "pn.pane.DeckGL(r, sizing_mode='stretch_width', tooltips=tooltips, height=600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that when specifying tooltips using pydeck you must also reference any data fields using the `{properties.<DATA_FIELD_NAME>}` syntax."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Controls\n",
    "\n",
    "The `DeckGL` pane exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "deck_gl_example = pn.pane.DeckGL(r, sizing_mode='stretch_width', tooltips=tooltips, height=600)\n",
    "\n",
    "pn.Row(deck_gl_example.controls(), deck_gl_example)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
